/**
 * mLibVec3d.js
 * Created: April 2013
 * Source link: http://code.google.com/p/code-depository/source/browse/
 *
 * By Suresh Nalluri,
 * License: The MIT License (MIT)
 *          See LICENSE details
 *
 * Requires:
 *	mLibFactory.js
 *	mLibIncludes.js
 *
 *	Todo:
 *		1. may have to create a new Class for Point3 as we have no way of distinguishing
 *		  a point from vector.
 *
 * 3D Vector Class
 * A 3D Vector is composed of x, y and z components
 *
 *
 * note this constructor pattern allows us to create objects in 2 ways:
 * 1. var o = mLib.Vec3(x, y, z);      //OR
 * 2. var o = new mLib.Vec3(x,y,z);
 *
 * NOTE: var o = [new] mLIB.Vec3(<no params>); // will return default vector (1.0, 0.0, 0.0);
 *
 * added benefit of this pattern is that we get a copy constructor by default!!
 * eg. var newVec = mLIB.Vec2(existingVec);
 * so no need to have an explicit copyOf method.
 *
 * Usage: 
 *		var o = [new] mLIB.Vec2([ x,y | Vec2 | [] ])
 *
 *-------------------------------------------------------
 **/

if (typeof mLIB == 'undefined') function mLIB() {};

( function() {
	mLIB.Vec3 = Factory.obj( {
		init: function() {
			var nargs = arguments.length;


			if ( nargs == 0 ) {
				// no args. default vector
                this.x = 1.0;
                this.y = 0.0;
                this.z = 0.0;
			} else if ( nargs == 1 ) {
				// arg is vector
				var t = arguments[0];
				this.x = t.x;
				this.y = t.y;
				this.z = t.z;
			} else if ( nargs == 2 ) {
				// to points p, q.  vector is p-q
				var p = arguments[0], q = arguments[1];
				this.x = p.x - q.x;
				this.y = p.y - q.y;
				this.z = p.z - q.z;
			} else if ( nargs == 3 ) {
				// given direction as x,y,z
                this.x = arguments[0];
                this.y = arguments[1];
                this.z = arguments[2];
			} else {
                // arguments given don't meet our criteria. set to default vector
                this.x = 1.0;
                this.y = 0.0;
                this.z = 0.0;
			}
						
		},  // func init

        // length - measured from the origin (0,0,0)
        length: function() {
            var x = this.x,
                y = this.y,
                z = this.z;
            return mLIB.Includes.sqrt( x*x + y*y + z*z );
        },
        
        lengthSquared: function() {
            var x = this.x,
                y = this.y,
                z = this.z;
            return x*x + y*y + z*z;
        },
        
        // tests for equality
        isEqual : function(v) { 
            return (
				mLIB.Includes.isZero(this.x - v.x) &&
				mLIB.Includes.isZero(this.y - v.y) &&
				mLIB.Includes.isZero(this.z - v.z)
            );

        },
        
        isNotEqual: function(v) {
            return !(
                mLIB.Includes.isZero(this.x - v.x) &&
                mLIB.Includes.isZero(this.y - v.y) &&
                mLIB.Includes.isZero(this.z - v.z)
            );
        },
        
        //test for zero vector
        isZero: function() {
            var x = this.x,
                y = this.y,
                z = this.z;
            return mLIB.Includes.isZero( x*x + y*y + z*z);
        },
        
        // test for unit vector
        isUnit: function() {
            var x = this.x,
                y = this.y,
                z = this.z;
            return mLIB.Includes.isZero( 1.0 - x*x - y*y - z*z );
        },

        // zero the elements of self that are close to zero.
        roundToZero: function() {
            if ( mLIB.Includes.isZero( this.x ) ) this.x = 0.0;
            if ( mLIB.Includes.isZero( this.y ) ) this.y = 0.0;
            if ( mLIB.Includes.isZero( this.z ) ) this.z = 0.0;
            return this;
        },

        // set all elements to zero
        zero: function() {
            this.x = this.y = this.z = 0.0;
            return this;
        },
        
        // normalise self
        normalise: function() {
            var x = this.x,
                y = this.y,
                z = this.z,
                lsq = x*x + y*y + z*z;
            
            if ( mLIB.Includes.isZero(lsq) ) {
                this.x = this.y = this.z = 0.0;
            } else
            {
                var scale = 1 / mLIB.Includes.sqrt( lsq );
                this.x *= scale;
                this.y *= scale;
                this.z *= scale;
            }
            return this;
        },

        // return a new vector, which is the Normalised version of this.
        Normalise: function() {
            var x = this.x,
                y = this.y,
                z = this.z,
                lsq = x*x + y*y + z*z;
            
            if ( mLIB.Includes.isZero(lsq) ) {
                return mLIB.Vec3(0, 0, 0);
            } else
            {
                var scale = 1.0 / mLIB.Includes.sqrt( lsq );
				return mLIB.Vec3(x*scale, y*scale, z*scale);
           }
        },

        // add a vector to self
        plus: function(v) {
            this.x += v.x;
            this.y += v.y;
            this.z += v.z;
            return this;
        },
        
        //return a new vector made  of self + v
        Plus: function(v) {
            return new mLIB.Vec3(this.x+v.x, this.y+v.y, this.z+v.z);
        },

        // subtract vector from self
        minus: function(v) {
            this.x -= v.x;
            this.y -= v.y;
            this.z -= v.z;
            return this;
        },
        
        //return a new vector made  of self - v
        Minus: function(v) {
            return new mLIB.Vec3(this.x-v.x, this.y-v.y, this.z-v.z);
        },

        // set the vector as the negation of self
        negation: function() {
            this.x = -this.x;
            this.y = -this.y;
            this.z = -this.z;
            return this;
        },
        
        // return a new vector which is the negation of self
        Negation: function() {
            return new mLIB.Vec3(-this.x, -this.y, -this.z);
        },
        
        // scale self - scalar multiplication
        scaleUp: function(scale) {
            this.x *= scale;
            this.y *= scale;
            this.z *= scale;
            return this;
        },
        
        // scale self and return as new vector - scalar multiplication
        ScaleUp: function(scale) {
            return new mLIB.Vec3(this.x*scale, this.y*scale, this.z*scale);
        },
        
        // scale self - scalar division
        scaleDown: function(scale) {
            // if scale is zero, then an error is thrown up.  let the app handle that
            this.x /= scale;
            this.y /= scale;
            this.z /= scale;
            return this;
        },
        
        // scale self and return as new vector - scalar division
        ScaleDown: function(scale) {
            // if scale is zero, then an error is thrown up.  let the app handle that
            return new mLIB.Vec3(this.x/scale, this.y/scale, this.z/scale);
        },

        // dot product of self with a vector
        dot: function(v) {
            return ( this.x*v.x + this.y*v.y + this.z*v.z );
        },

        // Cross product of set self as self X vector
        // useful for times when it's ok to clobber the this vector.
        cross: function(v) {
            var Ux = this.x, Vx = v.x,
                Uy = this.y, Vy = v.y,
                Uz = this.z, Vz = v.z;
            
            this.x = Uy*Vz-Uz*Vy;
            this.y = Uz*Vx-Ux*Vz;
            this.z = Ux*Vy-Uy*Vx;
            return this;
        },
        
        // Cross product of self with a vector - return new
        Cross: function(v) {
            var Ux = this.x, Vx = v.x,
                Uy = this.y, Vy = v.y,
                Uz = this.z, Vz = v.z;
            return new mLIB.Vec3( Uy*Vz-Uz*Vy, Uz*Vx-Ux*Vz, Ux*Vy-Uy*Vx );
        },

        // copy/assignment methods for a vector
        copy: function(v) {
            if ( v !== this ) {
                this.x = v.x;
                this.y = v.y;
                this.z = v.z;
            }
            return this;
        },

        Copy: function() {
            return new mLIB.Vec3(this.x, this.y, this.z);
        },
		
		toArray: function() {
			return [this.x, this.y, this.z].join('');
		},
        
        // accessor and setter methods
        set:  function(x, y, z) { this.x=x; this.y=y; this.z=z; return this; },
        setX: function(x) { this.x = x; return this; },
        setY: function(y) { this.y = y; return this; },
        setZ: function(z) { this.z = z; return this; },
        getX: function() { return this.x },
        getY: function() { return this.y },
        getZ: function() { return this.z },
        
        //debug
        log: function() {
			return ['(', this.x, ',', this.y, ',', this.z, ')'].join("");
        }
	} );  //mLIB.Vec3 = Factory.obj(
	
	mLIB.Point3 = mLIB.Vec3;		// 3D point defined using the Vec3 class type for now.
									// se Todo note 1. above

    // friend methods ---------------------------------------------
    //
    var friends = {

        Copy: function(v) {
            return new mLIB.Vec3(v.x, v.y, v.z);
        },
        
        // distance between 2 points
        distance: function(p0, p1) {
            var x = p0.x - p1.x,
                y = p0.y - p1.y,
                z = p0.z - p1.z;
            return mLIB.Includes.sqrt( x*x + y*y + z*z );
        },
        
        distanceSquared: function(p0, p1) {
            var x = p0.x - p1.x,
                y = p0.y - p1.y,
                z = p0.z - p1.z;
            return x*x + y*y + z*z;
        },
        
        // scaling a vector - multipliaction, return results inline object
        scaleUp: function(scale, v) {
            v.x *= scale;
            v.y *= scale;
            v.z *= scale;
            return v;
        },

        // scaling a vector - division, return results inline object
        scaleDown: function(scale, v) {
            v.x /= scale;
            v.y /= scale;
            v.z /= scale;
            return v;
        },

        // adding 2 vectors
        Plus: function(v1, v2) {
            return new mLIB.Vec3(v1.x+v2.x, v1.y+v2.y, v1.z+v2.z);
        },

        // subtracting vectors
        Minus: function(v1, v2) {
            return new mLIB.Vec3(v1.x-v2.x, v1.y-v2.y, v1.z-v2.z);
        },

        // adding 2 vectors - return into inline object
        plus: function(v1, v2, out) {
            out.x = v1.x+v2.x;
            out.y = v1.y+v2.y;
            out.z = v1.z+v2.z;
            return out;
        },

        // subtracting vectors - return into inline object
        minus: function(v1, v2, out) {
            out.x = v1.x-v2.x;
            out.y = v1.y-v2.y;
            out.z = v1.z-v2.z;
            return out;
        },

        // dot product
        dot: function(v1, v2) {
            return (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z);
        },
        
        // cross product
        Cross: function(u, v) {
            var Ux = u.x, Vx = v.x,
                Uy = u.y, Vy = v.y,
                Uz = u.z, Vz = v.z;
            return new mLIB.Vec3( Uy*Vz-Uz*Vy, Uz*Vx-Ux*Vz, Ux*Vy-Uy*Vx );
        },

		// check for class type
		isClass: function(theObj, objClass) {
			return Factory.isClass(theObj, objClass);
		},
		
		// return class type of a given object
		getClass: function(theObj) {
			return Factory.getClass(theObj);
		},
		
		// set up our 3d Cartesian space with origin and basis vectors
		origin: new mLIB.Point3(0.0, 0.0, 0.0),
		xAxis:	new mLIB.Vec3(1.0, 0.0, 0.0),	// i
		yAxis:	new mLIB.Vec3(0.0, 1.0, 0.0),	// j
		zAxis:	new mLIB.Vec3(0.0, 0.0, 1.0)	// k
		
    }; // friends
    
    for (var f in friends) {
        friends.hasOwnProperty(f) && (mLIB.Vec3[f] = friends[f]);
    };
    
} )();
